package com.atguigu.daijia.order.testLock;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 使用Redison实现分布式锁
     * 开发步骤：
     * 1.使用RedissonClient客户端对象 创建锁对象
     * 2.调用获取锁方法
     * 3.执行业务逻辑
     * 4.将锁释放
     *
     */
    public void testLock() {

        //创建锁对象
        RLock lock = redissonClient.getLock("lock1");

        //尝试加锁
        //lock() 阻塞等待一直到获取锁,默认锁有效期30s
//        lock.lock();

        //获取到锁：锁的过期时间10s
//        lock.lock(10, TimeUnit.SECONDS);

        try {
            //第一个参数获取锁等待时间 30s  第二个参数获取到锁过期时间 10s
            boolean isLock = lock.tryLock(6, 10, TimeUnit.SECONDS);
            if (isLock){
                //模拟业务执行时间超长 50s
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
                String value = redisTemplate.opsForValue().get("num");
                //2.如果值为空则非法直接返回即可
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //3.对num值进行自增加一
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));

                //4.将锁释放
                lock.unlock();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


    }


    /**
     * 通过Lua脚本，来保证判断和删锁的原子性
     */
    public  void testLock5() {
        //获取到当前的锁 使用setnx
        String uuid = String.valueOf(UUID.randomUUID());
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,10, TimeUnit.SECONDS);

        //true表示拿到锁
        if (isLock){
            //如果获取到锁，从Redis获取数据 数据+1 放到Redis
            String value = (String)this.redisTemplate.opsForValue().get("num");

            if (StringUtils.isBlank(value)){
                //需要用LUA脚本来保证原子性
//                //删除锁的时候，判断锁是否是自己的
//                if (uuid.equals(redisTemplate.opsForValue().get("lock"))){
//                    redisTemplate.delete("lock");
//                }
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                redisScript.setScriptText(script);
                redisScript.setResultType(Long.class);
                //执行判断和删锁逻辑
                redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
                return;
            }
            int num = Integer.parseInt(value);
            ++ num ;
            System.out.println("num的值为：" + num);
            redisTemplate.opsForValue().set("num", String.valueOf(num));
            //释放锁
            //需要用LUA脚本来保证原子性
//            if (uuid.equals(redisTemplate.opsForValue().get("lock"))){
//                redisTemplate.delete("lock");
//            }
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisScript.setScriptText(script);
            redisScript.setResultType(Long.class);
            //执行判断和删锁逻辑
            redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
        }else{
            //没有获取到，等待一会，继续调用
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 通过UUID来解决锁被别人释放案例
     */
    public void testLock4() {
        //获取到当前的锁 使用setnx
        String uuid = String.valueOf(UUID.randomUUID());
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,10, TimeUnit.SECONDS);

        //true表示拿到锁
        if (isLock){
            //如果获取到锁，从Redis获取数据 数据+1 放到Redis
            String value = (String)this.redisTemplate.opsForValue().get("num");

            if (StringUtils.isBlank(value)){
                //删除锁的时候，判断锁是否是自己的
                if (uuid.equals(redisTemplate.opsForValue().get("lock"))){
                    redisTemplate.delete("lock");
                }
                return;
            }
            int num = Integer.parseInt(value);
            ++ num ;
            System.out.println("num的值为：" + num);
            redisTemplate.opsForValue().set("num", String.valueOf(num));
            //释放锁
            //删除锁的时候，判断锁是否是自己的
            if (uuid.equals(redisTemplate.opsForValue().get("lock"))){
                redisTemplate.delete("lock");
            }
        }else{
            //没有获取到，等待一会，继续调用
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }


    /**
     * 使用Redis中的SetNx来实现分布式锁
     */
    public  void testLock3() {
        //获取到当前的锁 使用setnx
//        Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
        //通过设置过期时间防止死锁情况
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent("lock", "lock",10, TimeUnit.SECONDS);

        //true表示拿到锁
        if (isLock){
            //如果获取到锁，从Redis获取数据 数据+1 放到Redis
            String value = (String)this.redisTemplate.opsForValue().get("num");

            if (StringUtils.isBlank(value)){
                redisTemplate.delete("lock");
                return;
            }
            int num = Integer.parseInt(value);
            ++ num ;
            System.out.println("num的值为：" + num);
            redisTemplate.opsForValue().set("num", String.valueOf(num));
            //释放锁
            redisTemplate.delete("lock");
        }else{
            //没有获取到，等待一会，继续调用
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 本地锁的演示
     */
    public  synchronized void testLock1() {
        // 查询Redis中的num值
        String value = (String)this.redisTemplate.opsForValue().get("num");
        // 没有该值return
        if (StringUtils.isBlank(value)){
            return ;
        }
        // 有值就转成成int
        int num = Integer.parseInt(value);
        ++ num;
        System.out.println("num的值为：" + num);
        // 把Redis中的num值+1
        this.redisTemplate.opsForValue().set("num", String.valueOf(num));
    }
}